dash_charts.gantt_chart⚓︎
Gantt Chart.
Note: does not support resources nor task dependencies; however those could be added by extending this base class.
Removed Code⚓︎
# Just snippets of Python code that may be useful in the future
dates = sorted(set(filter(None, df_raw['start'].to_list() + df_raw['end'].to_list())))
self.axis_range = {'x': [dates[0], dates[-1]]}
View Source
"""Gantt Chart.
Note: does not support resources nor task dependencies; however those could be added by extending this base class.
# Removed Code
```py
# Just snippets of Python code that may be useful in the future
dates = sorted(set(filter(None, df_raw['start'].to_list() + df_raw['end'].to_list())))
self.axis_range = {'x': [dates[0], dates[-1]]}
”“”
import plotly.graph_objects as go from palettable.tableau import TableauMedium_10
from .utils_data import format_unix, get_unix from .utils_fig import CustomChart
class GanttChart(CustomChart): # noqa: H601 “”“Gantt Chart: task and milestone timeline.”“”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | |
```
Classes⚓︎
GanttChart⚓︎
class GanttChart(
*,
title,
xlabel,
ylabel,
layout_overrides=()
)
View Source
class GanttChart(CustomChart): # noqa: H601
"""Gantt Chart: task and milestone timeline."""
date_format = '%Y-%m-%d'
"""Date format for bar chart."""
pallette = TableauMedium_10.hex_colors
"""Default color pallette for project colors."""
hover_label_settings = {'bgcolor': 'white', 'font_size': 12, 'namelength': 0}
"""Plotly hover label settings."""
rh = 1
"""Height of each rectangular task."""
def create_traces(self, df_raw):
"""Return traces for plotly chart.
Args:
df_raw: pandas dataframe with columns: `(category, label, start, end, progress)`
Returns:
list: Dash chart traces
"""
# If start is None, assign end to start so that the sort is correct
start_index = df_raw.columns.get_loc('start')
end_index = df_raw.columns.get_loc('end')
for index in [idx for idx, is_na in enumerate(df_raw['start'].isna()) if is_na]:
df_raw.iloc[index, start_index] = df_raw.iloc[index, end_index]
df_raw['progress'] = df_raw['progress'].fillna(0) # Fill possibly missing progress values for milestones
df_raw = (
df_raw
.sort_values(by=['category', 'start'], ascending=False)
.sort_values(by=['end'], ascending=False)
.reset_index(drop=True)
)
# Create color lookup using categories in sorted order
categories = set(df_raw['category'])
self.color_lookup = {cat: self.pallette[idx] for idx, cat in enumerate(categories)}
# Track which categories have been plotted
plotted_categories = []
# Create the Gantt traces
traces = []
for task in df_raw.itertuples():
y_pos = task.Index
is_first = task.category not in plotted_categories
plotted_categories.append(task.category)
traces.append(self._create_task_shape(task, y_pos, is_first))
if task.progress > 0:
traces.append(self._create_progress_shape(task, y_pos))
traces.append(self._create_annotation(task, y_pos))
return traces
def _create_hover_text(self, task):
"""Return hover text for given trace.
Args:
task: row tuple from df_raw with: `(category, label, start, end, progress)`
Returns:
string: HTML-formatted hover text
"""
dates = [format_unix(get_unix(str_ts, self.date_format), '%a, %d%b%Y') for str_ts in [task.start, task.end]]
if task.start != task.end:
date_range = f'<br><b>Start</b>: {dates[0]}<br><b>End</b>: {dates[1]}'
else:
date_range = f'<br><b>Milestone</b>: {dates[1]}'
return f'<b>{task.category}</b><br>{task.label} ({int(task.progress * 100)}%)<br>{date_range}'
def _create_task_shape(self, task, y_pos, is_first):
"""Create colored task scatter rectangle.
Args:
task: row tuple from df_raw with: `(category, label, start, end, progress)`
y_pos: top y-coordinate of task
is_first: if True, this is the first time a task of this category will be plotted
Returns:
trace: single Dash chart Scatter trace
"""
color = self.color_lookup[task.category]
scatter_kwargs = {
'fill': 'toself',
'fillcolor': color,
'hoverlabel': self.hover_label_settings,
'legendgroup': color,
'line': {'width': 1},
'marker': {'color': color},
'mode': 'lines',
'showlegend': is_first,
'text': self._create_hover_text(task),
'x': [task.start, task.end, task.end, task.start, task.start],
'y': [y_pos, y_pos, y_pos - self.rh, y_pos - self.rh, y_pos],
}
if is_first:
scatter_kwargs['name'] = task.category
return go.Scatter(**scatter_kwargs)
def _create_progress_shape(self, task, y_pos):
"""Create semi-transparent white overlay `self.shapes` to indicate task progress.
Args:
task: row tuple from df_raw with: `(category, label, start, end, progress)`
y_pos: top y-coordinate of task
Returns:
trace: single Dash chart Scatter trace
"""
unix_start = get_unix(task.start, self.date_format)
unix_progress = (get_unix(task.end, self.date_format) - unix_start) * task.progress + unix_start
end = format_unix(unix_progress, self.date_format)
return go.Scatter(
fill='toself',
fillcolor='white',
hoverinfo='skip',
legendgroup=self.color_lookup[task.category],
line={'width': 1},
marker={'color': 'white'},
mode='lines',
opacity=0.5,
showlegend=False,
x=[task.start, end, end, task.start, task.start],
y=[y_pos, y_pos, y_pos - self.rh, y_pos - self.rh, y_pos],
)
def _create_annotation(self, task, y_pos):
"""Add task label to chart as text overlay.
Args:
task: row tuple from df_raw with: `(category, label, start, end, progress)`
y_pos: top y-coordinate of task
Returns:
trace: single Dash chart Scatter trace
"""
# For milestones with narrow fill, hover can be tricky, so intended to make the whole length of the text
# hoverable, but only the x/y point appears to be hoverable although it makes a larger hover zone at least
return go.Scatter(
hoverlabel=self.hover_label_settings,
hovertemplate=self._create_hover_text(task) + '<extra></extra>',
hovertext=self._create_hover_text(task),
legendgroup=self.color_lookup[task.category],
mode='text',
showlegend=False,
text=task.label,
textposition='middle left',
x=[task.end],
y=[y_pos - self.rh / 2],
)
def create_layout(self):
"""Extend the standard layout.
Returns:
dict: layout for Dash figure
"""
layout = super().create_layout()
# Suppress Y axis ticks/grid
layout['yaxis']['showgrid'] = False
layout['yaxis']['showticklabels'] = False
layout['yaxis']['zeroline'] = False
return layout
Ancestors (in MRO)⚓︎
- dash_charts.utils_fig.CustomChart
Class variables⚓︎
annotations
date_format
Date format for bar chart.
hover_label_settings
Plotly hover label settings.
pallette
Default color pallette for project colors.
rh
Height of each rectangular task.
Instance variables⚓︎
axis_range
Specify x/y axis range or leave as empty dictionary for autorange.
Methods⚓︎
apply_custom_layout⚓︎
def apply_custom_layout(
self,
layout
)
Extend and/or override layout with custom settings.
Parameters:
| Name | Description |
|---|---|
| layout | base layout dictionary. Typically from self.create_layout() |
Returns:
| Type | Description |
|---|---|
| dict | layout for Dash figure |
View Source
def apply_custom_layout(self, layout):
"""Extend and/or override layout with custom settings.
Args:
layout: base layout dictionary. Typically from self.create_layout()
Returns:
dict: layout for Dash figure
"""
for parent_key, sub_key, value in self.layout_overrides:
if sub_key is not None:
layout[parent_key][sub_key] = value
else:
layout[parent_key] = value
return layout
create_figure⚓︎
def create_figure(
self,
df_raw,
**kwargs_data
)
Create the figure dictionary.
Parameters:
| Name | Description |
|---|---|
| df_raw | data to pass to formatter method |
| kwargs_data | keyword arguments to pass to the data formatter method |
Returns:
| Type | Description |
|---|---|
| dict | keys data and layout for Dash |
View Source
def create_figure(self, df_raw, **kwargs_data):
"""Create the figure dictionary.
Args:
df_raw: data to pass to formatter method
kwargs_data: keyword arguments to pass to the data formatter method
Returns:
dict: keys `data` and `layout` for Dash
"""
return {
'data': self.create_traces(df_raw, **kwargs_data),
'layout': go.Layout(self.apply_custom_layout(self.create_layout())),
}
create_layout⚓︎
def create_layout(
self
)
Extend the standard layout.
Returns:
| Type | Description |
|---|---|
| dict | layout for Dash figure |
View Source
def create_layout(self):
"""Extend the standard layout.
Returns:
dict: layout for Dash figure
"""
layout = super().create_layout()
# Suppress Y axis ticks/grid
layout['yaxis']['showgrid'] = False
layout['yaxis']['showticklabels'] = False
layout['yaxis']['zeroline'] = False
return layout
create_traces⚓︎
def create_traces(
self,
df_raw
)
Return traces for plotly chart.
Parameters:
| Name | Description |
|---|---|
| df_raw | pandas dataframe with columns: (category, label, start, end, progress) |
Returns:
| Type | Description |
|---|---|
| list | Dash chart traces |
View Source
def create_traces(self, df_raw):
"""Return traces for plotly chart.
Args:
df_raw: pandas dataframe with columns: `(category, label, start, end, progress)`
Returns:
list: Dash chart traces
"""
# If start is None, assign end to start so that the sort is correct
start_index = df_raw.columns.get_loc('start')
end_index = df_raw.columns.get_loc('end')
for index in [idx for idx, is_na in enumerate(df_raw['start'].isna()) if is_na]:
df_raw.iloc[index, start_index] = df_raw.iloc[index, end_index]
df_raw['progress'] = df_raw['progress'].fillna(0) # Fill possibly missing progress values for milestones
df_raw = (
df_raw
.sort_values(by=['category', 'start'], ascending=False)
.sort_values(by=['end'], ascending=False)
.reset_index(drop=True)
)
# Create color lookup using categories in sorted order
categories = set(df_raw['category'])
self.color_lookup = {cat: self.pallette[idx] for idx, cat in enumerate(categories)}
# Track which categories have been plotted
plotted_categories = []
# Create the Gantt traces
traces = []
for task in df_raw.itertuples():
y_pos = task.Index
is_first = task.category not in plotted_categories
plotted_categories.append(task.category)
traces.append(self._create_task_shape(task, y_pos, is_first))
if task.progress > 0:
traces.append(self._create_progress_shape(task, y_pos))
traces.append(self._create_annotation(task, y_pos))
return traces
initialize_mutables⚓︎
def initialize_mutables(
self
)
Initialize the mutable data members to prevent modifying one attribute and impacting all instances.
View Source
def initialize_mutables(self):
"""Initialize the mutable data members to prevent modifying one attribute and impacting all instances."""
...
Created: August 5, 2022